﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Alexus.DDrawScene.PaintJobs;
using Microsoft.DirectX.DirectDraw;

namespace Alexus.DDrawScene
{
	/// <summary>
	///  содержит информацию о Мире и мировом окне, отрисовывает содержимое мирового окна туда, куда скажут.
	///  представляет Мир для пользователя в координатах от (0,0) до (1,1) вместо пикселей
	/// </summary>
	class World
	{
		//Surface _backgroundTexture;
		//Surface objectLayer;
		readonly Device _drawDevice;

		readonly LinkedList<PaintJob> _jobs = new LinkedList<PaintJob>();

		readonly ObjectDrawer _objDrawer;
		Surface _world;
		readonly Bitmap _worldPic;
		Point _worldViewPoint;


		public World(Device drawDevice, Bitmap worldPic)
		{
			_worldPic = worldPic;
			_drawDevice = drawDevice;
			CrerateSurfaces();
			_objDrawer = new ObjectDrawer(WorldBounds, drawDevice);
		}


		Rectangle WorldBounds { get { return new Rectangle(0, 0, _world.SurfaceDescription.Width, _world.SurfaceDescription.Height); } }

		//Bitmap WorldPicture
		//{
		//    get { return _worldPic; }
		//    set
		//    {
		//        _worldViewPoint = Point.Empty;
		//        Jobs.Clear();
		//        _worldPic = value;
		//        CrerateSurfaces();
		//        _objDrawer = new ObjectDrawer(WorldBounds, _drawDevice);
		//    }
		//}

		public LinkedList<PaintJob> Jobs { get { return _jobs; } }

		void CrerateSurfaces()
		{
			_world = new Surface(_worldPic, new SurfaceDescription(), _drawDevice);

			/*SurfaceDescription olDesc = new SurfaceDescription();
			olDesc.Width = _world.SurfaceDescription.Width;
			olDesc.Height = _world.SurfaceDescription.Height;*/
			//objectLayer = new Surface(olDesc, _drawDevice);
			//_backgroundTexture = getBackgroundTexture();
		}

		//Surface getBackgroundTexture()
		//{
		//    const int TextureSize = 128;
		//    var desc = new SurfaceDescription();
		//    desc.Width = TextureSize;
		//    desc.Height = TextureSize;
		//    var ret = new Surface(desc, _drawDevice);
		//    ret.FillColor = Color.Brown;
		//    ret.ForeColor = Color.Orange;
		//    ret.ColorFill(Color.Brown);
		//    for (int i = 0; i < TextureSize; i += 10)
		//    {
		//        ret.DrawLine(i, 0, 0, i);
		//    }
		//    return ret;
		//}

		/// <summary>
		///  рисует содержимое Мирового окна на области dest целевой поверхности target.
		/// </summary>
		public void DrawOn(Surface target, Rectangle dest)
		{
			Rectangle desttmp = dest; // участок на вьюпорте, в который будет вестись отрисовка
			Rectangle worldRect = WorldBounds;
			Rectangle wwtmp = Rectangle.Empty;
			// задаём позицию мирового окна из текущей позиции, а размеры - из размеров вьюпорта
			wwtmp.Location = _worldViewPoint;
			wwtmp.Size = dest.Size;
			if (wwtmp.Right > worldRect.Right || wwtmp.Left < worldRect.Left)
			{
// если мировое окно вылезло за границу мира
				// сдвигаем его так, чтобы правая граница была равнвна границе мира
				int dxRight = wwtmp.Right - worldRect.Right;
				int dxLeft = wwtmp.Left - worldRect.Left;
				if (dxRight > 0) wwtmp.X -= dxRight;
				if (dxLeft < 0) wwtmp.X -= dxLeft;
				if (wwtmp.Left < worldRect.Left || wwtmp.Right > worldRect.Right)
				{
					// если при этом опять левая или правая граница вылезла за Мир
					// значит, вьюпорт больше чем Мир. Значит, уменьшаем вьюпорт до размеров мира
					desttmp.Width = worldRect.Width;
					//desttmp.X -= wwtmp.X / 2;
					desttmp.X += (dest.Width - desttmp.Width) / 2;
					// и мировое окно тоже.
					wwtmp.X = 0;
					wwtmp.Width = worldRect.Width;
				}
			}
			// теперь то же самое - по вертикали
			if (wwtmp.Bottom > worldRect.Bottom || wwtmp.Top < worldRect.Top)
			{
				int dyBottom = wwtmp.Bottom - worldRect.Bottom;
				int dyTop = wwtmp.Top - worldRect.Top;
				if (dyBottom > 0) wwtmp.Y -= dyBottom;
				if (dyTop < 0) wwtmp.Y -= dyTop;
				if (wwtmp.Top < worldRect.Top || wwtmp.Bottom > worldRect.Bottom)
				{
					desttmp.Height = worldRect.Height;
					desttmp.Y += (dest.Height - desttmp.Height) / 2;
					wwtmp.Y = 0;
					wwtmp.Height = worldRect.Height;
				}
			}

			_objDrawer.Clear();
			LinkedListNode<PaintJob> job = Jobs.First;
			while (job != null)
			{
				if (job.Value.IsAlive)
				{
					job.Value.DrawOn(_objDrawer);
					job = job.Next;
				} else
				{
					LinkedListNode<PaintJob> job2del = job;
					job = job.Next;
					Jobs.Remove(job2del);
				}
			}


			if (wwtmp.Height > 0 && wwtmp.Width > 0)
			{
				try
				{
					target.Draw(desttmp, _world, wwtmp, DrawFlags.Wait);
				} catch (SurfaceLostException)
				{
					CrerateSurfaces();
				}
				_objDrawer.DrawOnViewport(target, desttmp, wwtmp);
			}
		}

		public void MoveWindow(int dx, int dy)
		{
			Point wwtmp = _worldViewPoint;
			wwtmp.X += dx;
			wwtmp.Y += dy;
			Rectangle worldRect = WorldBounds;

			if (wwtmp.X > worldRect.Left && wwtmp.X < worldRect.Right) _worldViewPoint.X = wwtmp.X;
			if (wwtmp.Y > worldRect.Top && wwtmp.Y < worldRect.Bottom) _worldViewPoint.Y = wwtmp.Y;
		}
	}

	/// <summary>
	///  //расширяет класс Мира функциями отрисовки объектов на нём
	///  //фишка в том, что все функции отрисовки работают в координатах (0,0)-(1,1), а не в пикселях
	/// 
	/// создаёт поверхность для отрисовки заданных размеров и рисует на ней всякие вещи в координатах (0,0)-(1,1), а не в пикселях
	/// </summary>
	public class ObjectDrawer
	{
		const int DefaultPenColor = 0x000000;
		const int OpactyColor = 0xFF00FF;
		readonly Stack<Color> _colorStack = new Stack<Color>();
		readonly Device _dev;
		Surface _objLayer;
		//LinkedList<WorldPaintJob> Jobs = new LinkedList<WorldPaintJob>();

		Rectangle _worldBounds;

		public ObjectDrawer(Rectangle worldBounds, Device drawDevice)
		{
			_colorStack.Push(Color.FromArgb(DefaultPenColor));
			_worldBounds = worldBounds;
			_dev = drawDevice;
			CreateObjectlayer();
		}

		Color DrawColor
		{
			get { return _colorStack.Peek(); }
			set
			{
				_colorStack.Pop();
				_colorStack.Push(value);
				_objLayer.ForeColor = value;
			}
		}

		#region --- Функции для рисования ---
		public void DrawRect(RectangleF rect)
		{
			Rectangle r = Convert(rect);
			_objLayer.DrawBox(r.Left, r.Top, r.Right, r.Bottom);
		}

		public void DrawLine(PointF start, PointF end)
		{
			Point s = Convert(start);
			Point f = Convert(end);
			_objLayer.DrawLine(s.X, s.Y, f.X, f.Y);
		}

		public void DrawPicture(Bitmap picture, RectangleF position)
		{
			var desc = new SurfaceDescription();
			//desc.AlphaBitDepth = 8;
			var pic = new Surface(picture, desc, _dev);
			var ckey = new ColorKey();
			//Color transpColor = Color.White; //.GetPixel(0, 0);
			ckey.ColorSpaceHighValue = 0xCCCCCC;
			ckey.ColorSpaceLowValue = 0;
			pic.SetColorKey(ColorKeyFlags.SourceDraw, ckey);
			Rectangle pos = Convert(position);
			_objLayer.Draw(pos, pic, DrawFlags.Wait | DrawFlags.KeySource | DrawFlags.AlphaSource);
			//_objLayer.DrawFast(pos.X, pos.Y, pic, DrawFastFlags.SourceColorKey);
			pic.Dispose();
		}

		public void Clear()
		{
			_objLayer.ColorFill(OpactyColor);
			//_objLayer.ColorFill(Color.FromArgb(0xFF,Color.Blue));
		}
		#endregion

		void CreateObjectlayer()
		{
			var caps = new SurfaceCaps();
			//caps.Alpha = true;
			var desc = new SurfaceDescription(caps);
			desc.Width = _worldBounds.Width;
			desc.Height = _worldBounds.Height;
			//PixelFormat pf = desc.PixelFormatStructure;
			///pf.Alpha = true;
			//pf.AlphaBitDepth = 4;
			//desc.PixelFormatStructure = pf;
			_objLayer = new Surface(desc, _dev);
			var key = new ColorKey
			{
				ColorSpaceHighValue = OpactyColor, 
				ColorSpaceLowValue = OpactyColor
			};
			_objLayer.SetColorKey(ColorKeyFlags.SourceDraw, key);
		}

		public void PushDrawColor(Color color)
		{
			Color col;
			int argb = color.ToArgb() & 0x00FFFFFF;
			if (argb == OpactyColor) col = Color.FromArgb(argb - 0x000100);
			else col = color;
			_colorStack.Push(col);
			_objLayer.ForeColor = col;
		}

		public Color PopDrawColor()
		{
			Color ret = _colorStack.Pop();
			_objLayer.ForeColor = _colorStack.Peek();
			return ret;
		}

		public void DrawOnViewport(Surface screen, Rectangle viewport, Rectangle window) { screen.Draw(viewport, _objLayer, window, DrawFlags.Wait | DrawFlags.KeySource | DrawFlags.AlphaSource); }

		#region ---Конвертеры---
		Rectangle Convert(RectangleF source)
		{
			var ret = new Rectangle(
				(int)(source.X * _worldBounds.Width),
				(int)(source.Y * _worldBounds.Height),
				(int)(source.Width * _worldBounds.Width),
				(int)(source.Height * _worldBounds.Height));
			return ret;
		}

		RectangleF Convert(Rectangle source)
		{
			var ret = new RectangleF(
				source.X / (float)_worldBounds.Width,
				source.Y / (float)_worldBounds.Height,
				source.Width / (float)_worldBounds.Width,
				source.Height / (float)_worldBounds.Height
				);
			return ret;
		}

		Point Convert(PointF source)
		{
			var ret = new Point(
				(int)(source.X * _worldBounds.Width),
				(int)(source.Y * _worldBounds.Height));
			return ret;
		}

		PointF Convert(Point source)
		{
			var ret = new PointF(
				source.X / (float)_worldBounds.Width,
				source.Y / (float)_worldBounds.Height);
			return ret;
		}
		#endregion
	}

	public class Scene
	{
		readonly Device _draw = null; // Holds the DrawDevice object.
		Surface _primaryFront;
		Point _lastMCoords;
		Surface _primaryBack;

		readonly Control _viewport;
		readonly World _world;

		public Scene(Bitmap wordPict, Control viewport)
		{
			_viewport = viewport;
			viewport.Paint += ViewportPaint;
			viewport.Resize += ViewportResize;
			viewport.MouseMove += ViewportMouseMove;
			viewport.MouseDown += ViewportMouseDown;

			_draw = new Device();
			_draw.SetCooperativeLevel(viewport, CooperativeLevelFlags.Normal);
			_world = new World(_draw, wordPict);

			//***dbg AddPaintJob(new RectanglePaintJob(new RectangleF((float)0.3, (float)0.3, (float)0.4, (float)0.4),Color.Blue));
			CreateSurfaces(); // Call the function that creates the surface objects.
		}

		/// <summary>
		/// This function is where the surfaces and
		/// clipper object are created.
		/// </summary>
		void CreateSurfaces()
		{
			if (_primaryFront != null)
			{
				_primaryFront.Restore();
				_primaryBack.Restore();
			} else
			{
				var description = new SurfaceDescription(); // Describes a surface.
				description.SurfaceCaps.PrimarySurface = true; //Capabilities of the surface.            

				_primaryFront = new Surface(description, _draw); // Create the surface using the description above.
			}

			var clip = new Clipper(_draw) // Create a new clipper.
			{
				Window = _viewport
			}; 
			_primaryFront.Clipper = clip; // Assign this clipper to the primary surface.

			SurfaceDescription desc = _primaryFront.SurfaceDescription;
			_primaryBack = new Surface(desc, _draw); // Get the attached surface that matches the caps, which will be the backbuffer.            
			_primaryBack.ForeColor = Color.White;
			_primaryBack.Clipper = clip; // Assign this clipper to the primary surface.


			/*SurfaceDescription description = new SurfaceDescription(); // Create a new SurfaceDescription struct.

			description.SurfaceCaps.PrimarySurface = true; // The caps for this surface is simply primary.

			primary = new Surface(description, _draw); // Create the primary surface.*/
		}


		public void Draw()
		{
			if (_primaryFront.Clipper.Window == null) _primaryFront.Clipper.Window = _viewport; // бывает, обнуляется, если с окошком делать всякие непотребства.
			// Get the new client size to Draw to.
			var destination = new Rectangle(_viewport.PointToScreen(new Point(0, 0)), _primaryFront.Clipper.Window.Size);


			try
			{
				// Try and Draw the offscreen surface on to the primary surface.
				_primaryBack.ColorFill(SystemColors.ButtonFace);
				_world.DrawOn(_primaryBack, destination);
				_primaryFront.Draw(_primaryBack, DrawFlags.Wait);
			} catch (SurfaceLostException)
			{
				// The surface can be lost if power saving
				// mode kicks in, or any other number of
				// reasons.
				CreateSurfaces(); // Surface was lost. Recreate them.
			}
		}

		public void AddPaintJob(PaintJob job) { _world.Jobs.AddLast(job); }
		public void RemovePaintJob(PaintJob job) { _world.Jobs.Remove(job); }

		//public RectangleF WindowPosition { get { return ww.WindowPosition; } }

		void ViewportPaint(object sender, PaintEventArgs e) { Draw(); }

		void ViewportResize(object sender, EventArgs e)
		{
			/*if (primary != null) {
				ww.ResizeWindow (primary.Clipper.Window.ClientSize);
				//ww.Resize(primary.Clipper.Window.ClientSize);
				Draw();

			}*/
		}

		void ViewportMouseMove(object sender, MouseEventArgs e)
		{
			if (e.Button != MouseButtons.Left) return;
			_world.MoveWindow(_lastMCoords.X - e.X, _lastMCoords.Y - e.Y);

			_lastMCoords = e.Location;
			Draw();
		}


		void ViewportMouseDown(object sender, MouseEventArgs e) { _lastMCoords = e.Location; }
	}
}